package com.zsrt.javaweb.ch01;

import java.util.Arrays;
import java.util.List;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

/**
 * 怎么把一个类声明为一个切面?
 * step1: 需要把这个类纳入到spring ioc容器的管理  @Component
 * step2: 需要把这个类声明为一个切面 @Aspect
 * step3: 需要告诉spring invokeBeforeMethod这个方法需要在哪些类的哪些方法开始之前执行
 * 		  @Before注解 用来说明该方法是一个前置通知[在目标方法开始之前执行]
 * 		  切点表达式
 * step4: 在applicationContext.xml中增加配置使得前置通知@Before起作用
 * 
 * step5: 如何接收到方法的参数 Joinpoint jp
 * 
 * @author scott
 *
 */
@Order(2)
@Component
@Aspect
public class LoggingAspect {
	
	
	/**
	 *  定义一个方法  用来描述切入点表达式[注意：该方法名字可以任意 里面不需要什么代码  因为是用发射读取有@Pointcut注解的方法]。
	 *  好处：如果切入点表达式发生了变化 只需要修改一个地方就行
	 *  
	 *  如果定义：
	 *  1：在方法上增加注解  @Pointcut 并且把切入点表达式写在注解里面
	 *  2：在其他的通知中直接使用方法名来引用这个方法名表示的切入点表达式
	 *  
	 */
	
	@Pointcut("execution(* com.zsrt.javaweb.ch01.*.*(..))")
	public void expression(){}
	
	/**
	 * 在方法之前执行
	 * 方法的访问修饰符public
	 * 方法的返回值int
	 * 方法所在的包名com.zsrt.javaweb.ch01
	 * 方法所在的类名ArithmeticCalculateImpl或者接口名ArithmeticCalculate
	 * 方法名add
	 * 方法的参数
	 */
	//@Before("execution(public int com.zsrt.javaweb.ch01.ArithmeticCalculateImpl.add(int, int))")
	//@Before("execution(private int com.zsrt.javaweb.ch01.ArithmeticCalculateImpl.*(int, int))")
	//@Before("execution(* com.zsrt.javaweb.ch01.ArithmeticCalculateImpl.*(int, int))")
	//@Before("execution(* com.zsrt.javaweb.ch01.*.*(int, int))")
	//@Before("execution(* com.zsrt.javaweb.ch01.*.*(..))")
	@Before("expression()")
	public void invokeBeforeMethod(JoinPoint jp){
		Signature sign = jp.getSignature();
		//得到方法的名字
		String name = sign.getName();
		
		//得到方法的参数
		Object[] args = jp.getArgs();
		List<Object> as = Arrays.asList(args);
		System.out.println("方法"+name+"的参数是"+as);
		
		
		System.out.println("[日志-->前置通知]在目标方法执行之前执行的代码...");
	}
	
	/**
	 * 后置通知 在目标方法执行之后[无论是否发生异常都会执行]执行的通知。
	 * 
	 * 后置通知是在连接点完成之后执行的。即连接点[目标类的方法]返回结果或者抛出异常的时候。
	 * 一个切面可以包含一个或者多个通知。
	 * 
	 * 重点注意：在后置通知中不能访问目标方法执行的结果  在返回通知中可以
	 */
	@After("expression()")
	public void  invokeAfterMethod(JoinPoint jp){
		Signature sign = jp.getSignature();
		//得到方法的名字
		String name = sign.getName();
		
		//得到方法的参数
		Object[] args = jp.getArgs();
		List<Object> as = Arrays.asList(args);
		System.out.println("方法"+name+"的参数是"+as);
		
		
		System.out.println("[--后置通知--] 在目标方法执行之后[无论是否发生异常都会执行]执行的通知...");
	}
	
	
	/**
	 * 返回通知：在目标方法正常结束[没有异常] 返回结果之后执行的通知   
	 * 返回通知可以得到目标方法执行的结果
	 * 
	 * 重点注意：如果目标方法出现异常  则返回通知不会执行
	 */
	@AfterReturning(value="expression()",returning="jieguo")
	public void invokeAfterReturning(JoinPoint jp,Object jieguo){
		Signature sign = jp.getSignature();
		//得到方法的名字
		String name = sign.getName();
		
		//得到方法的参数
		Object[] args = jp.getArgs();
		List<Object> as = Arrays.asList(args);
		System.out.println("方法"+name+"的参数是"+as);
		
		
		System.out.println("[--返回通知--] 在目标方法正常结束[没有异常] 返回结果之后执行的通知  执行的结果="+jieguo);
	}
	
	
	/**
	 * 异常通知 在目标方法出现异常的时候执行  而且可以访问到异常对象 可以指定在出现特定异常时才执行通知
	 * 
	 */
	@AfterThrowing(value="expression()",throwing="ex")
	public void  invokeAfterThrowing(JoinPoint jp,Exception ex){
	//public void  invokeAfterThrowing(JoinPoint jp,NullPointerException ex){
		Signature sign = jp.getSignature();
		//得到方法的名字
		String name = sign.getName();
		
		//得到方法的参数
		Object[] args = jp.getArgs();
		List<Object> as = Arrays.asList(args);
		System.out.println("方法"+name+"的参数是"+as);
		
		System.out.println("[--异常通知--] 在方法抛出异常之后执行"+ex);
	}
	
	
	/**
	 * 环绕通知  相当于前面4种通知的的集合 或者说是
	 * 环绕通知类似于动态代理的全过程。
	 * 
	 * 重点注意： 环绕通知 需要携带参数： ProceedingJoinPoint pjp  而且环绕通知必须要有返回值。返回值即为目标方法的返回值
	 * 
	 * 参数pjp 可以决定是否执行目标方法  
	 */
	//@Around("expression()")
	public Object invokeAround(ProceedingJoinPoint pjp){
		
		Signature sign = pjp.getSignature();
		//得到方法的名字
		String name = sign.getName();
		
		//得到方法的参数
		Object[] args = pjp.getArgs();
		List<Object> as = Arrays.asList(args);
		
		System.out.println("[环绕通知 ]方法"+name+"的参数是"+as);
		
		Object obj = null;
		
		//执行目标方法...
		try {
			System.out.println("[==模拟 前置通知==] ...");
			obj = pjp.proceed();
			System.out.println("[==模拟 返回通知==] ...");
			System.out.println("obj="+obj);
		} catch (Throwable e) {
			System.out.println("[==模拟 异常通知==] ...");
			e.printStackTrace();
		}
		System.out.println("[==模拟 后置通知==] ...");
		return obj;
	}

}
